﻿
using System.Text.RegularExpressions;

namespace Patterns4Net.ArchitectureExplorer.ViewModels
{
    using System.Collections.Generic;
    using System.Linq;
    using System;
    using System.Diagnostics.Contracts;
    using Infrastructure;
    using Infrastructure.Config;
    using Messages;
    using System.Collections.ObjectModel;
    using System.ComponentModel.Composition;
    using System.Windows;
    using Caliburn.Micro;
    using Services;
    using Core;

    [Export(typeof(SettingsViewModel))]
    public class SettingsViewModel : Screen
    {
        private readonly string settingsFilename;

        private readonly ISettingsSerializer settingsSerializer;

        private readonly IMessageBoxService messageBoxService;

        private readonly IEventAggregator eventAggregator;

        private bool wasOk;

        [ImportingConstructor]
        public SettingsViewModel(
            Settings settings,
            [Import("settingsFilename")]string settingsFilename,
            ISettingsSerializer settingsSerializer, 
            IMessageBoxService messageBoxService, 
            IEventAggregator eventAggregator)
        {
            Contract.Requires(settings != null);
            Contract.Requires(string.IsNullOrWhiteSpace(settingsFilename) == false);
            Contract.Requires(settingsSerializer != null);
            Contract.Requires(messageBoxService != null);

            this.settingsFilename = settingsFilename;
            this.settingsSerializer = settingsSerializer;
            this.messageBoxService = messageBoxService;
            this.eventAggregator = eventAggregator;
            this.Settings = settings;
            this.Logger = new NullLogger();
        }

        [Import]
        public ILog Logger { get; set; }

        public Settings Settings { get; private set; }

        public StringsListViewModel Namespaces { get; set; }

        public StringsListViewModel Types { get; set; }

        public StringsListViewModel Members { get; set; }

        public AttributesListViewModel GraphAttributes { get; set; }

        protected bool Changes
        {
            get
            {
                return this.Namespaces.Changed || 
                    this.Members.Changed || 
                    this.Types.Changed ||
                    this.GraphAttributes.Changed;
            }
        }

        public void SaveSettings()
        {
            this.Settings.IgnoredNamespaces.Clear();
            this.Namespaces.Items.ForEach(this.Settings.IgnoredNamespaces.Add);

            this.Settings.IgnoredTypes.Clear();
            this.Types.Items.ForEach(this.Settings.IgnoredTypes.Add);

            this.Settings.IgnoredMembers.Clear();
            this.Members.Items.ForEach(this.Settings.IgnoredMembers.Add);

            this.Settings.AdditionalGraphAttributes.Clear();
            this.GraphAttributes.Items.ForEach(this.Settings.AdditionalGraphAttributes.Add);

            try
            {
                this.settingsSerializer.Save(this.Settings, this.settingsFilename);
            }
            catch (Exception ex)
            {
                this.Logger.Error(ex);
                this.eventAggregator.Publish(
                    new ErrorMessage(
                        "The configuration cannot be saved into the 'Config/config.xml' file. " + 
                        "Make sure that Architecture Explorer has access rights for this folder. ",
                        true));
            }

            this.wasOk = true;
            this.TryClose();
        }

        public void Cancel()
        {
            this.TryClose();
        }

        public override void CanClose(System.Action<bool> callback)
        {
            if (this.Changes && this.wasOk == false)
            {
                var result = this.messageBoxService.ShowMessageBox(
                    "Your changes will not be applied. Do you still want to close the settings dialog?",
                    "Close?",
                    MessageBoxButton.YesNo,
                    MessageBoxImage.Question);
                callback(result == MessageBoxResult.Yes);
                return;
            }

            this.wasOk = false;
            callback(true);
        }

        protected override void OnActivate()
        {
            this.wasOk = false;
            this.DisplayName = "Architecture Explorer Settings";

            this.Namespaces = new StringsListViewModel(
                this.Settings.IgnoredNamespaces,
                () => {
                          this.Settings.SetUpDefaultIgnoredNamespaces();
                          return this.Settings.IgnoredNamespaces;
                      }, 
                      this.messageBoxService,
                      false);
            this.NotifyOfPropertyChange(() => this.Namespaces);

            this.Types = new StringsListViewModel(
                this.Settings.IgnoredTypes,
                () => {
                          this.Settings.SetUpDefaultIgnoredTypes();
                          return this.Settings.IgnoredTypes;
                      }, 
                      this.messageBoxService,
                      true);
            this.NotifyOfPropertyChange(() => this.Types);

            this.Members = new StringsListViewModel(
                this.Settings.IgnoredMembers,
                () => {
                          this.Settings.SetUpDefaultIgnoredMembers();
                          return this.Settings.IgnoredMembers;
                      }, 
                      this.messageBoxService,
                      true);
            this.NotifyOfPropertyChange(() => this.Members);

            this.GraphAttributes = new AttributesListViewModel(
                this.messageBoxService,
                this.Settings.AdditionalGraphAttributes,
                () =>
                {
                    this.Settings.SetUpDefaultGraphAttributes();
                    return this.Settings.AdditionalGraphAttributes;
                });
            this.NotifyOfPropertyChange(() => this.GraphAttributes);
        }
    }

    public class AttributesListViewModel : ItemsListViewModel<KeyValuePair<string, string>>
    {
        private readonly IMessageBoxService messageBoxService;

        public AttributesListViewModel(
            IMessageBoxService messageBoxService,
            IEnumerable<KeyValuePair<string, string>> items, 
            Func<IEnumerable<KeyValuePair<string, string>>> resetItems) : base(items, resetItems)
        {
            this.messageBoxService = messageBoxService;
        }

        public string NewKey { get; set; }

        public string NewValue { get; set; }

        public override void AddNew()
        {
            if (this.Items.Any(x => x.Key == this.NewKey))
            {
                this.messageBoxService.ShowMessageBox(
                    string.Format(
                        "Key '{0}' is already set. If you want to change it's value, remove it and add again.",
                        this.NewKey),
                    "Validation error",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
                return;
            }

            this.Items.Add(new KeyValuePair<string, string>(this.NewKey, this.NewValue));
            this.Changed = true;
        }
    }

    public class StringsListViewModel : ItemsListViewModel<string>
    {
        private readonly IMessageBoxService messageBoxService;

        private readonly bool isRegex;

        public StringsListViewModel(
            IEnumerable<string> items, 
            Func<IEnumerable<string>> resetItems, 
            IMessageBoxService messageBoxService,
            bool isRegex) 
            : base(items, resetItems)
        {
            this.messageBoxService = messageBoxService;
            this.isRegex = isRegex;
        }

        public string NewItem { get; set; }

        public override void AddNew()
        {
            if (this.isRegex && IsValidRegex(this.NewItem))
            {
                this.Items.Add(this.NewItem);
                this.Changed = true;
            }
            else
            {
                this.messageBoxService.ShowMessageBox(
                    string.Format("'{0}' is not valid reqular expression.", this.NewItem),
                    "Validation error",
                    MessageBoxButton.OK,
                    MessageBoxImage.Warning);
            }
        }

        private static bool IsValidRegex(string expression)
        {
            // unfortunatelly there is no other way to do it:
            try
            {
                new Regex(expression);
                return true;
            }
            catch
            {
                return false;
            }
        }
    }

    public abstract class ItemsListViewModel<T> : PropertyChangedBase
    {
        private readonly Func<IEnumerable<T>> resetItems;

        protected ItemsListViewModel(
            IEnumerable<T> items, 
            Func<IEnumerable<T>> resetItems)
        {
            this.Items = new ObservableCollection<T>(items.ToArray()); // copy the original items.
            this.resetItems = resetItems;
            this.Changed = false;
        }

        public bool Changed { get; protected set; }

        public void Reset()
        {
            this.Items = new ObservableCollection<T>(this.resetItems.Invoke());
            this.Changed = true;
            this.NotifyOfPropertyChange(() => this.Items);
        }

        public abstract void AddNew();

        public void Remove()
        {
            this.Items.Remove(this.SelectedItem);
            this.Changed = true;
        }

        public T SelectedItem { get; set; }        

        public ObservableCollection<T> Items { get; private set; }
    }
}
